package DramScript
{
	import flash.events.*;
	import flash.net.*;
	
	import mx.core.Application;
	
	import utils.Trace;
	import DramCore.GibGame;
	
	public class ScriptCompiler {
	 	
		protected  var _parentScriptExecuter:IScriptExecuter; // The one and only DramScript object
		protected var _scriptDefinition:ScriptDefinition ;
		protected var _statementDefArr:Array; // Defines the scripting language
		protected var _customTypeArr:Array; // Defines custom types - type "cwthing" param "bouncer"
		
		private static var _isDefArrPrepped:Boolean = false;
		private static var _MAXPARAMCOUNT:int = 64; // Statements can't have more than this many parameters
		private var _commandArr:Array ;// Command words
		private var _paramArr:Array; // Array of Parameter Arrays
		private var _commandTargetArr:Array;
		private var _lineIndex:int; // Current line being executed or stored
		private var _script_st:String;
		private var _actorArr:Array; //List of actors "Enter"ed
		private var _varObj:Object; // associative array of arrays of varnumber, varboolean, and varstring
		private var _intervalID:Number; // For setting inverval until script loads
		private var _loopCountArr:Array; // Loop, Endloop counter stack
		private var _inbuiltFunctionArr:Array; // "randomInteger"
		private var _game:GibGame;

		public function ScriptCompiler( scriptName:String, parentScriptExecuter:IScriptExecuter, scriptDefinitionClass:Class   )
		{
			trace("ScriptCompiler constructor: "+scriptName);
			this._parentScriptExecuter = parentScriptExecuter ;
			this._scriptDefinition = new scriptDefinitionClass();
			this._statementDefArr = _scriptDefinition._statementDefArr;
			this._customTypeArr = _scriptDefinition._customTypeArr;
			this._game = mx.core.Application.application._game;
			// Set the pointer to the DramScript object
			
			// Initialize instance properties
		 	_commandArr = new Array() ;// Command words
			_paramArr = new Array(); // Array of Parameter Arrays
			_commandTargetArr = new Array();
			_lineIndex = 0; // Current line being executed or stored
			_script_st = undefined;
			_actorArr = new Array(); //List of actors "Enter"ed
			// Create scriptable varnumbers, varstrings, varbooleans
			// These just hold the var names for validation
			// XXX if we silently fail to 1st choice do they need to be validated?
			// We'll start by being strict
			_varObj = new Object();
			_varObj.varstringinstanceArr = new Array();
			_varObj.varbooleaninstanceArr = new Array();
			_varObj.varnumberinstanceArr = new Array();
			_intervalID = 0; // For setting inverval until script loads
			_loopCountArr = new Array();
			_inbuiltFunctionArr = new Array("randominteger");
			// Make definition array lowercase and fill in ellipse-terminated definitions
			if(!_isDefArrPrepped ) {
				var word_st:String = "";
				var ellipsisFilled:Boolean;
				// Make _statementDefArr lower case
				for(var i:int = 0; i < _statementDefArr.length; i++) {
					ellipsisFilled = false;
					for(var j:int = 0; j < _statementDefArr[i].length; j++) {
						word_st = _statementDefArr[i][j];
						if( (word_st == "...")&&(!ellipsisFilled)){ // if param contains '...' fill to the end of line with '...'
							for(var k:int = j; k < (_MAXPARAMCOUNT+1) ;k++) {
								_statementDefArr[i].push("...");
							}
							ellipsisFilled = true;
							break;
						} else {
							_statementDefArr[i][j] = _statementDefArr[i][j].toLowerCase();
						}
					}
				}
			}
			loadScript(scriptName);
			trace("DramScriptCompiler constructed.");
		} // DramScriptCompiler constructor 

	// -------------------------------------------------------------------------
	// loadScript called by DramScript constructor
	private function loadScript(scriptName:String):void {
		Trace.myTrace("ScriptCompiler.loadScript: " +scriptName );
       	var loader:URLLoader = new URLLoader();
       	loader.dataFormat = URLLoaderDataFormat.TEXT;
       	loader.addEventListener(Event.COMPLETE, completeHandler,false,0,true);
		loader.addEventListener(IOErrorEvent.IO_ERROR, reportIOError,false,0,true);
        var request:URLRequest = new URLRequest(scriptName);
		try
		{
		    loader.load(request);
		}
		catch (error:Error)
        {
            Trace.throwError("Unable to load URL: "+scriptName+" ERROR: "+ error);
        }
		Trace.myTrace("ScriptCompiler.loadScript exit");
	}
	private function reportIOError( event:Event ):void {
		Trace.throwError("ScriptCompiler.loadScript IOError: "+event.toString() );
	}
	// When the script is done loading call parseFile() to validate it and then pass it to DramScript
	private function completeHandler(event:Event):void {
        var loader:URLLoader = URLLoader(event.target);
 		Trace.myTrace("completeHandler: " + loader.data);
    	parseFile( loader.data );
 	}
  	private function parseFile( src:String ):void {  	
		var success:Boolean = true;
			if (src != null ) {
				 _script_st = src;
 	 Trace.myTrace("ScriptCompiler.parseFile: "+ src);
				success = processScript();
				if(success) {
					success = checkIfLoopStructure();
					if(!success) {
						Trace.throwError("Error in If or Loop Structure");
					}
				}
				if(success) {
					_parentScriptExecuter.passParsedScript(_commandArr,_paramArr,_commandTargetArr,_actorArr);
				}
			} else {
				Trace.throwError( "Unable to load external file.");
			}
	} // onDataEvent
	
	// processScript()-----------------------------------------------------------
	// Called from onDataEvent so script is validated only after it is loaded -----
	// Loads script text into arrays 
	// Validate and store each line
	private function processScript():Boolean  {
		var success:Boolean = true;
		var lineArr:Array; // Array to store  each line of script
		var wordArr:Array; // Store each word in a line
		// Stuff script into lineArr by splitting it on \r's
		// There are blank lines and comments to remove
		lineArr = _script_st.split("\r");
			// Feed each line to the chopper
			for(var lineIndex:Number = 0; lineIndex < lineArr.length ; lineIndex++) {
				var foundGoodWord:Boolean = false;
				var storeWord:Boolean = false;
				var line_st:String = lineArr[lineIndex];
				var word_st:String = ""; // Current word being built
				var inQuote:Boolean = false;
				var inParens:Boolean = false; // for keeping spaces in randomInteger(n, m)
				wordArr = new Array(); // Array of words in a line with white space and comments removed
				// Skip empty lines
				if(line_st.length != 0) {
					//Go through each line, chop out the white space and comments and send it to validateStoreLine
					for(var charIndex:Number = 0; charIndex < line_st.length; charIndex++) {
						var thisChar:String = line_st.charAt(charIndex);
						var storeThisChar:Boolean = false;
						
						switch(thisChar) {
							case " ":
								if(inParens) { // don't store spaces between parens
									// do nothing
								} else {
									if(inQuote) { // Keep spaces within quotes
										storeThisChar = true;
									} else {
										if(foundGoodWord) { // Store the word in wordArr
											storeWord = true;
										}
									}
								}
							break;
							// treat comma's like spaces except store them between parens
							case ",":
								if(inQuote) { // Keep commas within quotes
									storeThisChar = true;
								} else {
									if(foundGoodWord) { // Store the word in wordArr
										storeWord = true;
									}
								}
							break;
							case "(": // Check for inbuilt functions like randominteger(n,m)
								 for(var i:int = 0; i < _inbuiltFunctionArr.length ; i++) {
									 if(word_st == _inbuiltFunctionArr[i]) {
										 inParens = true;
										 break;
									 } 
								 }
								 storeThisChar = true;
							break;
							case ")":
								inParens = false;
								storeThisChar = true;
							break;
							case "\"": // Double quote
								inQuote = !inQuote;
								storeThisChar = true;
							break;
							case "\n": // Ignore newline chars
							break;
							case "\t": // treat tabs as endwords
								if(foundGoodWord) { // Store the word in wordArr
									storeWord = true;
								}
							break;
							case "/": // Skip the rest of the line if "//" out of quote
								if(!inQuote) {
									if(line_st.charAt(charIndex+1) == "/"){
										charIndex = line_st.length; // Get out of the loop
									}
								} else {
									storeThisChar = true;
								}
							break;
							default: // Add character to current word
								foundGoodWord = true; // There is at least one character in this word
								word_st += line_st.charAt(charIndex);
							break;
						}	// switch(line_st.charAt(charIndex))
						if(storeThisChar) {
							word_st += thisChar;
							storeThisChar = false;
						}
						if(storeWord // At a space
						   // Or we're at the end of a line and the line length >0
						   || ((charIndex >= (line_st.length-1)) 
								 && (word_st.length > 0))) {
							if(!isNaN(Number(word_st))) { // Word is a number XXX add random(10,11) case
								wordArr.push(Number(word_st));
							} else { // Word is a string
								if(word_st.charAt(0) != "\"") { // Lowercase every non-quote word
									word_st = word_st.toLowerCase();  
								} else { // Strip quote marks from string
	//								word_st = word_st.slice(1,word_st.length -1 );
								}
								wordArr.push(word_st);
							}
							word_st = ""; // start new word
							foundGoodWord = false;
							storeWord = false;
						} // if (storeWord ...
					} // End of this line loop
					if(inParens) { 
							Trace.throwError("Unterminated paren: "+word_st);
					}
					if(inQuote) { 
							Trace.throwError("Unterminated double quote: "+word_st);
					}
					if(wordArr.length > 0 ){
						success = validateStoreLine(wordArr);
						if(!success) {
							Trace.throwError("Invalid line: "+wordArr);
						}
					}
//	 trace("processScript line_st: "+line_st);
//	 trace("processScript wordArr: "+wordArr+"\r");
				} // for(var lineIndex:Number = 0; lineIndex < lineArr.length ; lineIndex++) 
			}
		return success
	} // validateScript

	// validateStoreLine() ------------------------------------------------
	// Check syntax of line
	// Store list of Actors Entered, variables defined
	// Swap ActorName and Command on: Darwin Animate Fall
	// Store command and params
	// called from validateScript
	private function validateStoreLine(wordArr:Array):Boolean {
		var isValid:Boolean = false; 
		var statementIndex:int = 0;
		var i:int, j:int;
		var command:String = "";
		var ecr:ExecuteCommandResult;
		var lineDefArr:Array; // Store the command definition that matches current line
		
//	 trace("ScriptCompiler.validateStoreLine: "+wordArr);
		if(wordArr.length > _MAXPARAMCOUNT + 1) { // Param # + command
			Trace.throwError("Too many parameters. Max is: "+_MAXPARAMCOUNT);
		}
		// First check to see if first word is an actor name. If so, swap 1st 2 words
		for(i =0; i<_actorArr.length; i++) {
			var temp_st:String; // For swapping
			if(wordArr[0] == _actorArr[i]) {
				temp_st = wordArr[0];
				wordArr[0] = wordArr[1];
				wordArr[1] = temp_st;
				break;
			}
		}
		try {
			//Check to see that each line starts with a valid command
			for( statementIndex = 0;statementIndex < _statementDefArr.length;
						statementIndex++) {
				if(wordArr[0] == _statementDefArr[statementIndex][1]) {
					isValid = true;
					break;
				}
			}
			if(!isValid) {
				statementIndex = -1; // Don't try to print correct syntax
				Trace.throwError("First word is not a command: "+wordArr[0]+"");
			} else { // Command is okay, now check params
	
				// Chop off commandTarget and store command definition array
				lineDefArr = _statementDefArr[statementIndex].slice(1);
				if( wordArr.length > lineDefArr.length ) {
					Trace.throwError("Too many parameters");
				}
				// Too few parameters? Count only parameters not starting with "[" - optional
				for(i =0; i < lineDefArr.length;i++) {
					if ((lineDefArr[i].charAt(0) == "[") // parameter is optional
							|| (lineDefArr[i] == "...") ) { // or repeating terminal ...
						break;
					}
				}
				// Not enough params and current def is not "..."
				if((wordArr.length < i) && ( lineDefArr[i] != "...")) { 
					Trace.throwError("Too few parameters.");
				}
				// Do all newing of vars, actors and places
				switch ( String( wordArr[0] ).toUpperCase() ) { // build actor and variable arrays
				// If command is "NEWACTOR" add ActorName uniquely to actorArr
				// If command is "Enter" add ActorName uniquely to actorArr
					case "NEWACTOR":
						this._game.executeCommand("game", "SYS_NEWACTOR", new Array( wordArr[1], wordArr[2], wordArr[3]  ));
	// trace("actorArr: "+_actorArr);
					break;
					case "NEWPLACE":
						this._game.executeCommand("game", "SYS_NEWPLACE", new Array( wordArr[1], wordArr[2] ));
					break;
					case "VARSTRING":
					case "VARNUMBER":
					case "VARBOOLEAN":
						_varObj[wordArr[0]+"instanceArr"].push(wordArr[1]);
	// trace("_varObj[wordArr[0]+"Arr"]: "+_varObj[wordArr[0]+"Arr"]);
					break;

					default:
					break;
				} // switch (wordArr[0])
				if(lineDefArr.length > 0) { // There are parameters
					// Check parameter types
					var ellipses_type:String = "";
					
					for(var paramIndex:int = 1; paramIndex < wordArr.length; paramIndex++) { // i = 1 to skip command
						var thisParamType:String = "";
						var thisParam_st:String = String(wordArr[paramIndex]);
						var resultArr:Array = new Array();
						var defaultIndex:Number = 0; // For helping strip defaults off optional params
																			
						// If there is a "..." in expected params list
						// repeat last param
						// when there is one '...' in a def all the other param types are marked by '...' - done in constructor ??? XXX hack
						if(lineDefArr[paramIndex]=="...") { 
							if( i < 2 ) { // Not the 3rd or higher param
								Trace.throwError("'...' before required param type");
							}					
							// Store the type preceding the '...'
							if(ellipses_type == "") { 						
								ellipses_type = lineDefArr[paramIndex-1];
							}
							thisParamType = ellipses_type;
						} else {
							thisParamType = lineDefArr[paramIndex];
						}
						// Strip off defaults from optional parameter defs
						defaultIndex = thisParamType.indexOf("=");
						if( defaultIndex != -1) { // Default exists
							thisParamType = thisParamType.slice(0,defaultIndex);
						}
//trace("paramType: "+thisParamType);
						switch( thisParamType ) {
							case "string":
							case "[string]":
							// No errors - any paramater is a string
/*								resultArr = isLegalNumber(thisParam_st);
								if(resultArr[0]==true) {
									Trace.throwError("Found Number when expecting String - param# "+i+": "+wordArr[paramIndex]);
								} 
*/							break;
							case "boolean":
							case "[boolean]":
							if((thisParam_st != "true") && (thisParam_st != "false")) {
									Trace.throwError("Expected boolean: \"true\" or \"false\"");
								 }
							break;
							case "cw/ccw":
							case "[cw/ccw]":
								if((thisParam_st != "cw") && (thisParam_st != "ccw")) {
									Trace.throwError("Expected clockwise direction: \"cw\" or \"ccw\"");
								 }
							break;
							case "[quotestring]":
							case "quotestring":
							var test:String = thisParam_st.charAt(0);
								if(thisParam_st.charAt(0) == "\"") { 
									// Chop off enclosing double quotes
									wordArr[paramIndex] = thisParam_st.slice(1,thisParam_st.length -1 );
								} else {
									if(!isNaN(thisParam_st as Number)) {
										Trace.throwError("Found Number when expecting String within quotes - param# "+i+": "+thisParam_st);
									}
								}
								break;
							case "number":
							case "[number]":
								resultArr = isLegalNumber(thisParam_st);
								if(resultArr[0]==false) {
									Trace.throwError("Not a number - param# "+i+": "+thisParam_st);
								}
								if(resultArr.length > 2) { // there is a complex calced number array
									resultArr = resultArr.slice(1); // Shift off element 0 - the bool
									wordArr[paramIndex] = resultArr; // assign complex array for runtime calc
								}
								// if it's a simple number don't replace it
								break;
							case "actorname":
								ecr = mx.core.Application.application._game.executeCommand("game","SYS_FINDACTOR", new Array( thisParam_st));
								if( ecr.resString == "FAIL"){
									Trace.throwError("Expected ActorName that has already been NEWACTORed: "+thisParam_st);
								}
								break;
							case "placename":
								ecr = mx.core.Application.application._game.executeCommand("game","SYS_FINDPLACE", new Array( thisParam_st));
								if( ecr.resString == "FAIL"){
									Trace.throwError("Expected PlaceName that has already been NEWPLACEed: "+thisParam_st);
								}
							break;
							case "varstringinstance":
							case "varbooleaninstance":
							case "varnumberinstance":
								var varExists:Boolean = false;
								for(j = 0; j < _varObj[thisParamType+"Arr"].length ; j++) { // Has Actor already entered?
									if(thisParam_st == _varObj[thisParamType+"Arr"][j]) {
										varExists = true;
										break;
									} ;
								}
								if(!varExists) {
									Trace.throwError("Expected "+thisParamType+" to be already instantiated - param# "+paramIndex+"\r This "+thisParamType+": "+thisParam_st);
								}
								break;
							case "integer":
							case "[integer]":
								resultArr = isLegalNumber(thisParam_st);
									if(resultArr[0]==false) {
										Trace.throwError("Not a number - param# "+i+": "+thisParam_st);
									}
									if(resultArr.length > 2) { // there is a complex calced number array
										resultArr = resultArr.slice(1); // Shift off element 0 - the bool
										wordArr[paramIndex] = resultArr; // assign complex array for runtime calc
									}
								break;
							default:
								if( !checkCustomTypes(thisParamType, thisParam_st)){
									Trace.throwError("Problem with parameter typing routine.");
								}
							break;
						} // switch( thisParamType ) {
					} // for(var paramIndex = 1; paramIndex < wordArr.length; paramIndex++) 
				} //if(lineDefArr.length > 0) { // There are parameters
			}
		} catch (myError:Error ) {
			var errString:String = myError + " line #"+this._lineIndex+": "+wordArr;
			if(statementIndex != -1) { // Command recognized and here is its syntax
				errString +=  "  Correct syntax: "+lineDefArr;
			}
			Trace.throwError( errString );
			isValid = false; // For what it's worth
		}
		command = wordArr[0];
		wordArr = wordArr.slice(1); // Chop off command
//trace(".."+wordArr);
		// If the wordArr needs more parameters add defaults
		if(wordArr.length < (_statementDefArr[statementIndex].length-2)) {
//trace("wordArr.length: "+wordArr.length+ " _statementDefArr[statementIndex].length: "+_statementDefArr[statementIndex].length);
//trace("Before addDefaults: "+wordArr);
			wordArr = addDefaults(wordArr,_statementDefArr[statementIndex]);
//trace("After addDefaults: "+wordArr);
		}
		if(isValid) { // Store commands and params
			_commandArr.push(command);
			_paramArr.push(wordArr); // Check case of no params
			_commandTargetArr.push(_statementDefArr[statementIndex][0]);
		}
		return isValid 
	} // validateStoreLine
	// Checks parameters to see if they belong to a custom type
	private function checkCustomTypes(thisParamType:String, thisParam_st:String):Boolean {
		var result:Boolean = true;
		var foundType:Boolean = false;
		var t:int; // type index
		var p:int; // param index
		for( t = 0; t < this._customTypeArr.length; t++){
			if( thisParamType == this._customTypeArr[t][0] ) {
				foundType = true;
				break; // found the type
			}
		}
		if( !foundType ){
			Trace.throwError("No custom type: "+thisParamType);
		}
		for( p = 0; p < this._customTypeArr[t].length; p++){
			if( thisParam_st == this._customTypeArr[t][p] ){
				result == true;
				break;
			}
		}
		if( !result ){
			Trace.throwError("param: "+ thisParam_st + "is not type: "+ thisParamType );
		}
		return result;
	}
	private function addDefaults(paramArr:Array,targetDefArr:Array):Array {
		var needsDefaults:Boolean = true;
		var i:int;
		var defArr:Array = targetDefArr.slice(2); // Peel off command target and command
		if( paramArr.length >= defArr.length ) {
			Trace.throwError("No need for defaults");
		}
		// Fill paramArr with default values
		for(i = paramArr.length; i < defArr.length ; i++) { 
			var defaultValue_st:String;
			var defString:String = defArr[i];
			if(defString == "...") { // Don't add params for "..." params
				break;
			}
			if(defString.charAt(0) != "[") { // Not an optional parameter
				Trace.throwError("Parameter is not optional: "+defString);
			}
			var equalsIndex:int = defString.indexOf("=");
			if(equalsIndex != -1) {
				defaultValue_st = defString.slice(equalsIndex+1);
				// Add default values of form "paramN" which repeats previous parameter
				if(defaultValue_st.slice(0,5) == "param") {
					defaultValue_st = paramArr[defaultValue_st.slice(5)];
				}
				paramArr.push(defaultValue_st);
			}
//trace(paramArr);
		} // for(var i = paramArr.length; i < defArr.length ; i++) { 
		return paramArr;
	}
	// isLegalNumber ---------------------------------------------------------------------------
	// returns array - 
	//		resultArr[0]:Boolean - true if a number, 
	//		resultArr[1]:Array - [1][0] = type string "randominteger", "varnumber"...
	//			[1][1..] = params for type string . for "varnumber": varName_st. For "randominteger" minN,maxN
	// Check to see if this is a well-formed random, a varnumber, or simple number
	// eventualy a parsable formula
	// XXX Add extra param to check for integer? numberType:String "integer","number"
	private function isLegalNumber(param_st:String):Array {
		var resultArr:Array = new Array();
		if( param_st.charAt(0)=="\""  ) { 
			// Do nothing. False will be returned
		} else {
			// XXX AS3's string->Number casting has some problems
			// instead of passing back NaN for Number("123x") it returns a 0
			// so I will see if the string that returns a 0 is "0"
			// This is a very breakable hack
			var isNumber:Boolean = true;
			var checkNumber:Number = Number(param_st);
			if( isNaN(checkNumber) ) {
					isNumber = false;
			}
	
// trace("DSC.isLegalNumber param_st: "+param_st + " as Number: "+checkNumber+ " isNumber: "+ isNumber );
			if( isNumber ) { // if it's a simple number 
				resultArr.push(true);
				resultArr.push(Number(param_st));
			} else {// check for varnumber's
				for(var i:int =0; i < _varObj.varnumberinstanceArr.length; i++) {
					if(param_st == _varObj.varnumberinstanceArr[i]) { // It's a varnumber
						resultArr.push(true);
						var varArr:Array = new Array("varnumber",param_st);
						resultArr.push(varArr);
						break;
					}
				}
				if(resultArr.length == 0) { // check for well-formed randominteger
					resultArr = parseStringForm(param_st,new Array("randominteger(","integer",",","integer",")"));
				} 
			}
		}
		if(resultArr.length == 0) {
			resultArr.push(false);
//	trace("isLegalNumber() returns false for: "+param_st);
		}
		return resultArr;
	} 
	// parseStringForm() ------------------------------------------------------
	// Passed a test string and an array of sequential targets
	// Returns an array of form
	//		resultArr[0]:Boolean - true string is correct, 
	//		resultArr[1]:Array - [1][0] = type string "randominteger", "varnumber"...
	//			[1][1..] = params for type string . for "varnumber": varName_st. For "randominteger" minN,maxN
	private function parseStringForm(test_st:String,matchArr:Array):Array {
		var resultArr:Array = new Array();
		var wellFormed:Boolean = true;
		for(var matchArrIndex:Number = 0; matchArrIndex < matchArr.length; matchArrIndex++) {
			var thisTest_st:String = matchArr[matchArrIndex];
			switch ( thisTest_st ) {
				case "integer":
				case "number": // Using vars for readibility
					var num_st:String;
					if(matchArrIndex == matchArr.length-1) { // last param
						num_st = test_st;
					} else { // test chars up to next target index
						var matchLen:Number = test_st.indexOf( matchArr[matchArrIndex+1] ) 
						num_st = test_st.slice(0, matchLen);
						test_st = test_st.slice(matchLen);
					}
					if(!isNaN(num_st as Number)) { // It's a number
						resultArr.push(Number(num_st));
					} else {
						wellFormed = false;
					}
				break;
				default: // Match and store the whole test_st unless it is a punctuation 
					if(test_st.indexOf( thisTest_st ) != 0) {
						wellFormed = false;
					} else {
						test_st = test_st.slice(thisTest_st.length);
					}
					switch (thisTest_st){ // Don't store punctuation
						case ",":
						case ")":
						case "(":
						break;
						default: // add significant params to the result array
							resultArr.push(thisTest_st);
						break;
					}
				break;
			} // switch ( thisTest_st ) {
			if( wellFormed == false) {
				break;
			}
		} // for(var matchArrIndex:Number = 0; matchArrIndex
		resultArr.unshift(wellFormed); // Add the wellFormed bool to the front of the resultArr
		return resultArr;
	} // parseStringForm()
	

	// checkIfLoopStructure() --------------------------------------------------------
	// Check the Ifchoose, elseif, endif, Ifrandom, Loop, Endloop structure
	private function checkIfLoopStructure():Boolean {
		var ifArr:Array = new Array(); // keep track of nested structures
		var command:String;
		var success:Boolean = true;
		try {
			for(var i:int =0; i < _commandArr.length; i++) {
				command = _commandArr[i];
				switch(command) {
					case "loop":
						ifArr.push(new Array("loop"));
					break;
					// XXX Do I want to enforce 1 elseif for each param of IfChoose??
					case "elseif":
						if(ifArr.length ==0 ) { 
							Trace.throwError("Elseif doesn't follow IfXXX");
						} else { 
							if((--ifArr[ifArr.length-1][1]) == 0) {
								Trace.throwError("Too many elseif's for preceding "+ifArr[ifArr.length-1][0]);
							}
						}
					break;
					case "endloop":
						if(ifArr.length==0) {
							Trace.throwError("Extra Endloop");
						} else {
							if((ifArr[ifArr.length-1][0]=="ifchoose")
								||(ifArr[ifArr.length-1][0]=="ifrandom")) {
								Trace.throwError("Endloop in "+ifArr[ifArr.length-1][0]+" structure.");								
							} else {
									// Down one level in if structure
								ifArr.pop();
							}
						}
					break;
					case "endif":
						if(ifArr.length==0) {
							Trace.throwError("Extra Endif");
						} else {
							if(ifArr[ifArr.length-1][0]=="loop") {
								Trace.throwError("Endif in loop.");								
							} else {
								if((ifArr[ifArr.length-1][1]) > 1) {
									Trace.throwError("Too few elseif's for preceding "+ifArr[ifArr.length-1][0]);
								} else {
								// Down one level in if structure
									ifArr.pop();
								}
							}
						}
					break;
					case "ifchoose":
						// push if-type and number of choices
						ifArr.push(new Array("ifchoose",_paramArr[i].length));
					break;
					case "ifrandom":
						// Push if-type and random case count
						ifArr.push(new Array("ifrandom",Number(_paramArr[i][0])));
					break;
					default:
						// Any other commands that start with "if" get a binary "ifZZZ" structure stored	
						var ifIndex:Number = command.indexOf("if",0);
						if( ifIndex == 0) {
							// push if-type and number of choices
							ifArr.push(new Array(command,2));
						}
					break;
					break;
					
				}
			} // for(var i=0; i < _commandArr.length; i++)
			if(ifArr.length > 0 ) {
				Trace.throwError("Need "+ifArr.length+" more "+ifArr[ifArr.length-1][0]+" closure(s)");
			}
		} catch (myError:Error) {
			Trace.throwError( myError.message );
			success = false;
		}
		return success;
	} // checkIfLoopStructure()
	}


}